home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2005 October / PCWOCT05.iso / Software / FromTheMag / XAMPP 1.4.14 / xampp-win32-1.4.14-installer.exe / xampp / php / pear / spreadsheet / Excel / Writer / Parser.php < prev    next >
PHP Script  |  2003-12-15  |  67KB  |  1,761 lines

  1. <?php
  2. /**
  3. *  Class for parsing Excel formulas
  4. *
  5. *  License Information:
  6. *
  7. *    Spreadsheet_Excel_Writer:  A library for generating Excel Spreadsheets
  8. *    Copyright (c) 2002-2003 Xavier Noguer xnoguer@rezebra.com
  9. *
  10. *    This library is free software; you can redistribute it and/or
  11. *    modify it under the terms of the GNU Lesser General Public
  12. *    License as published by the Free Software Foundation; either
  13. *    version 2.1 of the License, or (at your option) any later version.
  14. *
  15. *    This library is distributed in the hope that it will be useful,
  16. *    but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  18. *    Lesser General Public License for more details.
  19. *
  20. *    You should have received a copy of the GNU Lesser General Public
  21. *    License along with this library; if not, write to the Free Software
  22. *    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  23. */
  24.  
  25. /**
  26. * @const SPREADSHEET_EXCEL_WRITER_ADD token identifier for character "+"
  27. */
  28. define('SPREADSHEET_EXCEL_WRITER_ADD',"+");
  29.  
  30. /**
  31. * @const SPREADSHEET_EXCEL_WRITER_SUB token identifier for character "-"
  32. */
  33. define('SPREADSHEET_EXCEL_WRITER_SUB',"-");
  34.  
  35. /**
  36. * @const SPREADSHEET_EXCEL_WRITER_MUL token identifier for character "*"
  37. */
  38. define('SPREADSHEET_EXCEL_WRITER_MUL',"*");
  39.  
  40. /**
  41. * @const SPREADSHEET_EXCEL_WRITER_DIV token identifier for character "/"
  42. */
  43. define('SPREADSHEET_EXCEL_WRITER_DIV',"/");
  44.  
  45. /**
  46. * @const SPREADSHEET_EXCEL_WRITER_OPEN token identifier for character "("
  47. */
  48. define('SPREADSHEET_EXCEL_WRITER_OPEN',"(");
  49.  
  50. /**
  51. * @const SPREADSHEET_EXCEL_WRITER_CLOSE token identifier for character ")"
  52. */
  53. define('SPREADSHEET_EXCEL_WRITER_CLOSE',")");
  54.  
  55. /**
  56. * @const SPREADSHEET_EXCEL_WRITER_COMA token identifier for character ","
  57. */
  58. define('SPREADSHEET_EXCEL_WRITER_COMA',",");
  59.  
  60. /**
  61. * @const SPREADSHEET_EXCEL_WRITER_SEMICOLON token identifier for character ";"
  62. */
  63. define('SPREADSHEET_EXCEL_WRITER_SEMICOLON',";");
  64.  
  65. /**
  66. * @const SPREADSHEET_EXCEL_WRITER_GT token identifier for character ">"
  67. */
  68. define('SPREADSHEET_EXCEL_WRITER_GT',">");
  69.  
  70. /**
  71. * @const SPREADSHEET_EXCEL_WRITER_LT token identifier for character "<"
  72. */
  73. define('SPREADSHEET_EXCEL_WRITER_LT',"<");
  74.  
  75. /**
  76. * @const SPREADSHEET_EXCEL_WRITER_LE token identifier for character "<="
  77. */
  78. define('SPREADSHEET_EXCEL_WRITER_LE',"<=");
  79.  
  80. /**
  81. * @const SPREADSHEET_EXCEL_WRITER_GE token identifier for character ">="
  82. */
  83. define('SPREADSHEET_EXCEL_WRITER_GE',">=");
  84.  
  85. /**
  86. * @const SPREADSHEET_EXCEL_WRITER_EQ token identifier for character "="
  87. */
  88. define('SPREADSHEET_EXCEL_WRITER_EQ',"=");
  89.  
  90. /**
  91. * @const SPREADSHEET_EXCEL_WRITER_NE token identifier for character "<>"
  92. */
  93. define('SPREADSHEET_EXCEL_WRITER_NE',"<>");
  94.  
  95.  
  96. require_once('PEAR.php');
  97.  
  98. /**
  99. * Class for parsing Excel formulas
  100. *
  101. * @author   Xavier Noguer <xnoguer@rezebra.com>
  102. * @category FileFormats
  103. * @package  Spreadsheet_Excel_Writer
  104. */
  105.  
  106. class Spreadsheet_Excel_Writer_Parser extends PEAR
  107. {
  108.     /**
  109.     * The index of the character we are currently looking at
  110.     * @var integer
  111.     */
  112.     var $_current_char;
  113.  
  114.     /**
  115.     * The token we are working on.
  116.     * @var string
  117.     */
  118.     var $_current_token;
  119.  
  120.     /**
  121.     * The formula to parse
  122.     * @var string
  123.     */
  124.     var $_formula;
  125.  
  126.     /**
  127.     * The character ahead of the current char
  128.     * @var string
  129.     */
  130.     var $_lookahead;
  131.  
  132.     /**
  133.     * The parse tree to be generated
  134.     * @var string
  135.     */
  136.     var $_parse_tree;
  137.  
  138.     /**
  139.     * The byte order. 1 => big endian, 0 => little endian.
  140.     * @var integer
  141.     */
  142.     var $_byte_order;
  143.  
  144.     /**
  145.     * Array of external sheets
  146.     * @var array
  147.     */
  148.     var $_ext_sheets;
  149.  
  150.     /**
  151.     * Array of sheet references in the form of REF structures
  152.     * @var array
  153.     */
  154.     var $_references;
  155.  
  156.     /**
  157.     * The BIFF version for the workbook
  158.     * @var integer
  159.     */
  160.     var $_BIFF_version;
  161.  
  162.     /**
  163.     * The class constructor
  164.     *
  165.     * @param integer $byte_order The byte order (Little endian or Big endian) of the architecture
  166.                                  (optional). 1 => big endian, 0 (default) little endian.
  167.     */
  168.     function Spreadsheet_Excel_Writer_Parser($byte_order, $biff_version)
  169.     {
  170.         $this->_current_char  = 0;
  171.         $this->_BIFF_version  = $biff_version;
  172.         $this->_current_token = '';       // The token we are working on.
  173.         $this->_formula       = "";       // The formula to parse.
  174.         $this->_lookahead     = '';       // The character ahead of the current char.
  175.         $this->_parse_tree    = '';       // The parse tree to be generated.
  176.         $this->_initializeHashes();      // Initialize the hashes: ptg's and function's ptg's
  177.         $this->_byte_order = $byte_order; // Little Endian or Big Endian
  178.         $this->_ext_sheets = array();
  179.         $this->_references = array();
  180.     }
  181.     
  182.     /**
  183.     * Initialize the ptg and function hashes. 
  184.     *
  185.     * @access private
  186.     */
  187.     function _initializeHashes()
  188.     {
  189.         // The Excel ptg indices
  190.         $this->ptg = array(
  191.             'ptgExp'       => 0x01,
  192.             'ptgTbl'       => 0x02,
  193.             'ptgAdd'       => 0x03,
  194.             'ptgSub'       => 0x04,
  195.             'ptgMul'       => 0x05,
  196.             'ptgDiv'       => 0x06,
  197.             'ptgPower'     => 0x07,
  198.             'ptgConcat'    => 0x08,
  199.             'ptgLT'        => 0x09,
  200.             'ptgLE'        => 0x0A,
  201.             'ptgEQ'        => 0x0B,
  202.             'ptgGE'        => 0x0C,
  203.             'ptgGT'        => 0x0D,
  204.             'ptgNE'        => 0x0E,
  205.             'ptgIsect'     => 0x0F,
  206.             'ptgUnion'     => 0x10,
  207.             'ptgRange'     => 0x11,
  208.             'ptgUplus'     => 0x12,
  209.             'ptgUminus'    => 0x13,
  210.             'ptgPercent'   => 0x14,
  211.             'ptgParen'     => 0x15,
  212.             'ptgMissArg'   => 0x16,
  213.             'ptgStr'       => 0x17,
  214.             'ptgAttr'      => 0x19,
  215.             'ptgSheet'     => 0x1A,
  216.             'ptgEndSheet'  => 0x1B,
  217.             'ptgErr'       => 0x1C,
  218.             'ptgBool'      => 0x1D,
  219.             'ptgInt'       => 0x1E,
  220.             'ptgNum'       => 0x1F,
  221.             'ptgArray'     => 0x20,
  222.             'ptgFunc'      => 0x21,
  223.             'ptgFuncVar'   => 0x22,
  224.             'ptgName'      => 0x23,
  225.             'ptgRef'       => 0x24,
  226.             'ptgArea'      => 0x25,
  227.             'ptgMemArea'   => 0x26,
  228.             'ptgMemErr'    => 0x27,
  229.             'ptgMemNoMem'  => 0x28,
  230.             'ptgMemFunc'   => 0x29,
  231.             'ptgRefErr'    => 0x2A,
  232.             'ptgAreaErr'   => 0x2B,
  233.             'ptgRefN'      => 0x2C,
  234.             'ptgAreaN'     => 0x2D,
  235.             'ptgMemAreaN'  => 0x2E,
  236.             'ptgMemNoMemN' => 0x2F,
  237.             'ptgNameX'     => 0x39,
  238.             'ptgRef3d'     => 0x3A,
  239.             'ptgArea3d'    => 0x3B,
  240.             'ptgRefErr3d'  => 0x3C,
  241.             'ptgAreaErr3d' => 0x3D,
  242.             'ptgArrayV'    => 0x40,
  243.             'ptgFuncV'     => 0x41,
  244.             'ptgFuncVarV'  => 0x42,
  245.             'ptgNameV'     => 0x43,
  246.             'ptgRefV'      => 0x44,
  247.             'ptgAreaV'     => 0x45,
  248.             'ptgMemAreaV'  => 0x46,
  249.             'ptgMemErrV'   => 0x47,
  250.             'ptgMemNoMemV' => 0x48,
  251.             'ptgMemFuncV'  => 0x49,
  252.             'ptgRefErrV'   => 0x4A,
  253.             'ptgAreaErrV'  => 0x4B,
  254.             'ptgRefNV'     => 0x4C,
  255.             'ptgAreaNV'    => 0x4D,
  256.             'ptgMemAreaNV' => 0x4E,
  257.             'ptgMemNoMemN' => 0x4F,
  258.             'ptgFuncCEV'   => 0x58,
  259.             'ptgNameXV'    => 0x59,
  260.             'ptgRef3dV'    => 0x5A,
  261.             'ptgArea3dV'   => 0x5B,
  262.             'ptgRefErr3dV' => 0x5C,
  263.             'ptgAreaErr3d' => 0x5D,
  264.             'ptgArrayA'    => 0x60,
  265.             'ptgFuncA'     => 0x61,
  266.             'ptgFuncVarA'  => 0x62,
  267.             'ptgNameA'     => 0x63,
  268.             'ptgRefA'      => 0x64,
  269.             'ptgAreaA'     => 0x65,
  270.             'ptgMemAreaA'  => 0x66,
  271.             'ptgMemErrA'   => 0x67,
  272.             'ptgMemNoMemA' => 0x68,
  273.             'ptgMemFuncA'  => 0x69,
  274.             'ptgRefErrA'   => 0x6A,
  275.             'ptgAreaErrA'  => 0x6B,
  276.             'ptgRefNA'     => 0x6C,
  277.             'ptgAreaNA'    => 0x6D,
  278.             'ptgMemAreaNA' => 0x6E,
  279.             'ptgMemNoMemN' => 0x6F,
  280.             'ptgFuncCEA'   => 0x78,
  281.             'ptgNameXA'    => 0x79,
  282.             'ptgRef3dA'    => 0x7A,
  283.             'ptgArea3dA'   => 0x7B,
  284.             'ptgRefErr3dA' => 0x7C,
  285.             'ptgAreaErr3d' => 0x7D
  286.             );
  287.     
  288.         // Thanks to Michael Meeks and Gnumeric for the initial arg values.
  289.         //
  290.         // The following hash was generated by "function_locale.pl" in the distro.
  291.         // Refer to function_locale.pl for non-English function names.
  292.         //
  293.         // The array elements are as follow:
  294.         // ptg:   The Excel function ptg code.
  295.         // args:  The number of arguments that the function takes:
  296.         //           >=0 is a fixed number of arguments.
  297.         //           -1  is a variable  number of arguments.
  298.         // class: The reference, value or array class of the function args.
  299.         // vol:   The function is volatile.
  300.         //
  301.         $this->_functions = array(
  302.               // function                  ptg  args  class  vol
  303.               'COUNT'           => array(   0,   -1,    0,    0 ),
  304.               'IF'              => array(   1,   -1,    1,    0 ),
  305.               'ISNA'            => array(   2,    1,    1,    0 ),
  306.               'ISERROR'         => array(   3,    1,    1,    0 ),
  307.               'SUM'             => array(   4,   -1,    0,    0 ),
  308.               'AVERAGE'         => array(   5,   -1,    0,    0 ),
  309.               'MIN'             => array(   6,   -1,    0,    0 ),
  310.               'MAX'             => array(   7,   -1,    0,    0 ),
  311.               'ROW'             => array(   8,   -1,    0,    0 ),
  312.               'COLUMN'          => array(   9,   -1,    0,    0 ),
  313.               'NA'              => array(  10,    0,    0,    0 ),
  314.               'NPV'             => array(  11,   -1,    1,    0 ),
  315.               'STDEV'           => array(  12,   -1,    0,    0 ),
  316.               'DOLLAR'          => array(  13,   -1,    1,    0 ),
  317.               'FIXED'           => array(  14,   -1,    1,    0 ),
  318.               'SIN'             => array(  15,    1,    1,    0 ),
  319.               'COS'             => array(  16,    1,    1,    0 ),
  320.               'TAN'             => array(  17,    1,    1,    0 ),
  321.               'ATAN'            => array(  18,    1,    1,    0 ),
  322.               'PI'              => array(  19,    0,    1,    0 ),
  323.               'SQRT'            => array(  20,    1,    1,    0 ),
  324.               'EXP'             => array(  21,    1,    1,    0 ),
  325.               'LN'              => array(  22,    1,    1,    0 ),
  326.               'LOG10'           => array(  23,    1,    1,    0 ),
  327.               'ABS'             => array(  24,    1,    1,    0 ),
  328.               'INT'             => array(  25,    1,    1,    0 ),
  329.               'SIGN'            => array(  26,    1,    1,    0 ),
  330.               'ROUND'           => array(  27,    2,    1,    0 ),
  331.               'LOOKUP'          => array(  28,   -1,    0,    0 ),
  332.               'INDEX'           => array(  29,   -1,    0,    1 ),
  333.               'REPT'            => array(  30,    2,    1,    0 ),
  334.               'MID'             => array(  31,    3,    1,    0 ),
  335.               'LEN'             => array(  32,    1,    1,    0 ),
  336.               'VALUE'           => array(  33,    1,    1,    0 ),
  337.               'TRUE'            => array(  34,    0,    1,    0 ),
  338.               'FALSE'           => array(  35,    0,    1,    0 ),
  339.               'AND'             => array(  36,   -1,    0,    0 ),
  340.               'OR'              => array(  37,   -1,    0,    0 ),
  341.               'NOT'             => array(  38,    1,    1,    0 ),
  342.               'MOD'             => array(  39,    2,    1,    0 ),
  343.               'DCOUNT'          => array(  40,    3,    0,    0 ),
  344.               'DSUM'            => array(  41,    3,    0,    0 ),
  345.               'DAVERAGE'        => array(  42,    3,    0,    0 ),
  346.               'DMIN'            => array(  43,    3,    0,    0 ),
  347.               'DMAX'            => array(  44,    3,    0,    0 ),
  348.               'DSTDEV'          => array(  45,    3,    0,    0 ),
  349.               'VAR'             => array(  46,   -1,    0,    0 ),
  350.               'DVAR'            => array(  47,    3,    0,    0 ),
  351.               'TEXT'            => array(  48,    2,    1,    0 ),
  352.               'LINEST'          => array(  49,   -1,    0,    0 ),
  353.               'TREND'           => array(  50,   -1,    0,    0 ),
  354.               'LOGEST'          => array(  51,   -1,    0,    0 ),
  355.               'GROWTH'          => array(  52,   -1,    0,    0 ),
  356.               'PV'              => array(  56,   -1,    1,    0 ),
  357.               'FV'              => array(  57,   -1,    1,    0 ),
  358.               'NPER'            => array(  58,   -1,    1,    0 ),
  359.               'PMT'             => array(  59,   -1,    1,    0 ),
  360.               'RATE'            => array(  60,   -1,    1,    0 ),
  361.               'MIRR'            => array(  61,    3,    0,    0 ),
  362.               'IRR'             => array(  62,   -1,    0,    0 ),
  363.               'RAND'            => array(  63,    0,    1,    1 ),
  364.               'MATCH'           => array(  64,   -1,    0,    0 ),
  365.               'DATE'            => array(  65,    3,    1,    0 ),
  366.               'TIME'            => array(  66,    3,    1,    0 ),
  367.               'DAY'             => array(  67,    1,    1,    0 ),
  368.               'MONTH'           => array(  68,    1,    1,    0 ),
  369.               'YEAR'            => array(  69,    1,    1,    0 ),
  370.               'WEEKDAY'         => array(  70,   -1,    1,    0 ),
  371.               'HOUR'            => array(  71,    1,    1,    0 ),
  372.               'MINUTE'          => array(  72,    1,    1,    0 ),
  373.               'SECOND'          => array(  73,    1,    1,    0 ),
  374.               'NOW'             => array(  74,    0,    1,    1 ),
  375.               'AREAS'           => array(  75,    1,    0,    1 ),
  376.               'ROWS'            => array(  76,    1,    0,    1 ),
  377.               'COLUMNS'         => array(  77,    1,    0,    1 ),
  378.               'OFFSET'          => array(  78,   -1,    0,    1 ),
  379.               'SEARCH'          => array(  82,   -1,    1,    0 ),
  380.               'TRANSPOSE'       => array(  83,    1,    1,    0 ),
  381.               'TYPE'            => array(  86,    1,    1,    0 ),
  382.               'ATAN2'           => array(  97,    2,    1,    0 ),
  383.               'ASIN'            => array(  98,    1,    1,    0 ),
  384.               'ACOS'            => array(  99,    1,    1,    0 ),
  385.               'CHOOSE'          => array( 100,   -1,    1,    0 ),
  386.               'HLOOKUP'         => array( 101,   -1,    0,    0 ),
  387.               'VLOOKUP'         => array( 102,   -1,    0,    0 ),
  388.               'ISREF'           => array( 105,    1,    0,    0 ),
  389.               'LOG'             => array( 109,   -1,    1,    0 ),
  390.               'CHAR'            => array( 111,    1,    1,    0 ),
  391.               'LOWER'           => array( 112,    1,    1,    0 ),
  392.               'UPPER'           => array( 113,    1,    1,    0 ),
  393.               'PROPER'          => array( 114,    1,    1,    0 ),
  394.               'LEFT'            => array( 115,   -1,    1,    0 ),
  395.               'RIGHT'           => array( 116,   -1,    1,    0 ),
  396.               'EXACT'           => array( 117,    2,    1,    0 ),
  397.               'TRIM'            => array( 118,    1,    1,    0 ),
  398.               'REPLACE'         => array( 119,    4,    1,    0 ),
  399.               'SUBSTITUTE'      => array( 120,   -1,    1,    0 ),
  400.               'CODE'            => array( 121,    1,    1,    0 ),
  401.               'FIND'            => array( 124,   -1,    1,    0 ),
  402.               'CELL'            => array( 125,   -1,    0,    1 ),
  403.               'ISERR'           => array( 126,    1,    1,    0 ),
  404.               'ISTEXT'          => array( 127,    1,    1,    0 ),
  405.               'ISNUMBER'        => array( 128,    1,    1,    0 ),
  406.               'ISBLANK'         => array( 129,    1,    1,    0 ),
  407.               'T'               => array( 130,    1,    0,    0 ),
  408.               'N'               => array( 131,    1,    0,    0 ),
  409.               'DATEVALUE'       => array( 140,    1,    1,    0 ),
  410.               'TIMEVALUE'       => array( 141,    1,    1,    0 ),
  411.               'SLN'             => array( 142,    3,    1,    0 ),
  412.               'SYD'             => array( 143,    4,    1,    0 ),
  413.               'DDB'             => array( 144,   -1,    1,    0 ),
  414.               'INDIRECT'        => array( 148,   -1,    1,    1 ),
  415.               'CALL'            => array( 150,   -1,    1,    0 ),
  416.               'CLEAN'           => array( 162,    1,    1,    0 ),
  417.               'MDETERM'         => array( 163,    1,    2,    0 ),
  418.               'MINVERSE'        => array( 164,    1,    2,    0 ),
  419.               'MMULT'           => array( 165,    2,    2,    0 ),
  420.               'IPMT'            => array( 167,   -1,    1,    0 ),
  421.               'PPMT'            => array( 168,   -1,    1,    0 ),
  422.               'COUNTA'          => array( 169,   -1,    0,    0 ),
  423.               'PRODUCT'         => array( 183,   -1,    0,    0 ),
  424.               'FACT'            => array( 184,    1,    1,    0 ),
  425.               'DPRODUCT'        => array( 189,    3,    0,    0 ),
  426.               'ISNONTEXT'       => array( 190,    1,    1,    0 ),
  427.               'STDEVP'          => array( 193,   -1,    0,    0 ),
  428.               'VARP'            => array( 194,   -1,    0,    0 ),
  429.               'DSTDEVP'         => array( 195,    3,    0,    0 ),
  430.               'DVARP'           => array( 196,    3,    0,    0 ),
  431.               'TRUNC'           => array( 197,   -1,    1,    0 ),
  432.               'ISLOGICAL'       => array( 198,    1,    1,    0 ),
  433.               'DCOUNTA'         => array( 199,    3,    0,    0 ),
  434.               'ROUNDUP'         => array( 212,    2,    1,    0 ),
  435.               'ROUNDDOWN'       => array( 213,    2,    1,    0 ),
  436.               'RANK'            => array( 216,   -1,    0,    0 ),
  437.               'ADDRESS'         => array( 219,   -1,    1,    0 ),
  438.               'DAYS360'         => array( 220,   -1,    1,    0 ),
  439.               'TODAY'           => array( 221,    0,    1,    1 ),
  440.               'VDB'             => array( 222,   -1,    1,    0 ),
  441.               'MEDIAN'          => array( 227,   -1,    0,    0 ),
  442.               'SUMPRODUCT'      => array( 228,   -1,    2,    0 ),
  443.               'SINH'            => array( 229,    1,    1,    0 ),
  444.               'COSH'            => array( 230,    1,    1,    0 ),
  445.               'TANH'            => array( 231,    1,    1,    0 ),
  446.               'ASINH'           => array( 232,    1,    1,    0 ),
  447.               'ACOSH'           => array( 233,    1,    1,    0 ),
  448.               'ATANH'           => array( 234,    1,    1,    0 ),
  449.               'DGET'            => array( 235,    3,    0,    0 ),
  450.               'INFO'            => array( 244,    1,    1,    1 ),
  451.               'DB'              => array( 247,   -1,    1,    0 ),
  452.               'FREQUENCY'       => array( 252,    2,    0,    0 ),
  453.               'ERROR.TYPE'      => array( 261,    1,    1,    0 ),
  454.               'REGISTER.ID'     => array( 267,   -1,    1,    0 ),
  455.               'AVEDEV'          => array( 269,   -1,    0,    0 ),
  456.               'BETADIST'        => array( 270,   -1,    1,    0 ),
  457.               'GAMMALN'         => array( 271,    1,    1,    0 ),
  458.               'BETAINV'         => array( 272,   -1,    1,    0 ),
  459.               'BINOMDIST'       => array( 273,    4,    1,    0 ),
  460.               'CHIDIST'         => array( 274,    2,    1,    0 ),
  461.               'CHIINV'          => array( 275,    2,    1,    0 ),
  462.               'COMBIN'          => array( 276,    2,    1,    0 ),
  463.               'CONFIDENCE'      => array( 277,    3,    1,    0 ),
  464.               'CRITBINOM'       => array( 278,    3,    1,    0 ),
  465.               'EVEN'            => array( 279,    1,    1,    0 ),
  466.               'EXPONDIST'       => array( 280,    3,    1,    0 ),
  467.               'FDIST'           => array( 281,    3,    1,    0 ),
  468.               'FINV'            => array( 282,    3,    1,    0 ),
  469.               'FISHER'          => array( 283,    1,    1,    0 ),
  470.               'FISHERINV'       => array( 284,    1,    1,    0 ),
  471.               'FLOOR'           => array( 285,    2,    1,    0 ),
  472.               'GAMMADIST'       => array( 286,    4,    1,    0 ),
  473.               'GAMMAINV'        => array( 287,    3,    1,    0 ),
  474.               'CEILING'         => array( 288,    2,    1,    0 ),
  475.               'HYPGEOMDIST'     => array( 289,    4,    1,    0 ),
  476.               'LOGNORMDIST'     => array( 290,    3,    1,    0 ),
  477.               'LOGINV'          => array( 291,    3,    1,    0 ),
  478.               'NEGBINOMDIST'    => array( 292,    3,    1,    0 ),
  479.               'NORMDIST'        => array( 293,    4,    1,    0 ),
  480.               'NORMSDIST'       => array( 294,    1,    1,    0 ),
  481.               'NORMINV'         => array( 295,    3,    1,    0 ),
  482.               'NORMSINV'        => array( 296,    1,    1,    0 ),
  483.               'STANDARDIZE'     => array( 297,    3,    1,    0 ),
  484.               'ODD'             => array( 298,    1,    1,    0 ),
  485.               'PERMUT'          => array( 299,    2,    1,    0 ),
  486.               'POISSON'         => array( 300,    3,    1,    0 ),
  487.               'TDIST'           => array( 301,    3,    1,    0 ),
  488.               'WEIBULL'         => array( 302,    4,    1,    0 ),
  489.               'SUMXMY2'         => array( 303,    2,    2,    0 ),
  490.               'SUMX2MY2'        => array( 304,    2,    2,    0 ),
  491.               'SUMX2PY2'        => array( 305,    2,    2,    0 ),
  492.               'CHITEST'         => array( 306,    2,    2,    0 ),
  493.               'CORREL'          => array( 307,    2,    2,    0 ),
  494.               'COVAR'           => array( 308,    2,    2,    0 ),
  495.               'FORECAST'        => array( 309,    3,    2,    0 ),
  496.               'FTEST'           => array( 310,    2,    2,    0 ),
  497.               'INTERCEPT'       => array( 311,    2,    2,    0 ),
  498.               'PEARSON'         => array( 312,    2,    2,    0 ),
  499.               'RSQ'             => array( 313,    2,    2,    0 ),
  500.               'STEYX'           => array( 314,    2,    2,    0 ),
  501.               'SLOPE'           => array( 315,    2,    2,    0 ),
  502.               'TTEST'           => array( 316,    4,    2,    0 ),
  503.               'PROB'            => array( 317,   -1,    2,    0 ),
  504.               'DEVSQ'           => array( 318,   -1,    0,    0 ),
  505.               'GEOMEAN'         => array( 319,   -1,    0,    0 ),
  506.               'HARMEAN'         => array( 320,   -1,    0,    0 ),
  507.               'SUMSQ'           => array( 321,   -1,    0,    0 ),
  508.               'KURT'            => array( 322,   -1,    0,    0 ),
  509.               'SKEW'            => array( 323,   -1,    0,    0 ),
  510.               'ZTEST'           => array( 324,   -1,    0,    0 ),
  511.               'LARGE'           => array( 325,    2,    0,    0 ),
  512.               'SMALL'           => array( 326,    2,    0,    0 ),
  513.               'QUARTILE'        => array( 327,    2,    0,    0 ),
  514.               'PERCENTILE'      => array( 328,    2,    0,    0 ),
  515.               'PERCENTRANK'     => array( 329,   -1,    0,    0 ),
  516.               'MODE'            => array( 330,   -1,    2,    0 ),
  517.               'TRIMMEAN'        => array( 331,    2,    0,    0 ),
  518.               'TINV'            => array( 332,    2,    1,    0 ),
  519.               'CONCATENATE'     => array( 336,   -1,    1,    0 ),
  520.               'POWER'           => array( 337,    2,    1,    0 ),
  521.               'RADIANS'         => array( 342,    1,    1,    0 ),
  522.               'DEGREES'         => array( 343,    1,    1,    0 ),
  523.               'SUBTOTAL'        => array( 344,   -1,    0,    0 ),
  524.               'SUMIF'           => array( 345,   -1,    0,    0 ),
  525.               'COUNTIF'         => array( 346,    2,    0,    0 ),
  526.               'COUNTBLANK'      => array( 347,    1,    0,    0 ),
  527.               'ROMAN'           => array( 354,   -1,    1,    0 )
  528.               );
  529.     }
  530.     
  531.     /**
  532.     * Convert a token to the proper ptg value.
  533.     *
  534.     * @access private
  535.     * @param mixed $token The token to convert.
  536.     * @return mixed the converted token on success. PEAR_Error if the token
  537.     *               is not recognized
  538.     */
  539.     function _convert($token)
  540.     {
  541.         if (preg_match("/^\"[^\"]{0,255}\"$/", $token))
  542.         {
  543.             return $this->_convertString($token);
  544.         }
  545.         elseif (is_numeric($token))
  546.         {
  547.             return $this->_convertNumber($token);
  548.         }
  549.         // match references like A1 or $A$1
  550.         elseif (preg_match('/^\$?([A-Ia-i]?[A-Za-z])\$?(\d+)$/',$token))
  551.         {
  552.             return $this->_convertRef2d($token);
  553.         }
  554.         // match external references like Sheet1!A1 or Sheet1:Sheet2!A1
  555.         elseif (preg_match("/^\w+(\:\w+)?\![A-Ia-i]?[A-Za-z](\d+)$/",$token))
  556.         {
  557.             return $this->_convertRef3d($token);
  558.         }
  559.         // match external references like Sheet1!A1 or Sheet1:Sheet2!A1
  560.         elseif (preg_match("/^'\w+(\:\w+)?'\![A-Ia-i]?[A-Za-z](\d+)$/",$token))
  561.         {
  562.             return $this->_convertRef3d($token);
  563.         }
  564.         // match ranges like A1:B2
  565.         elseif (preg_match("/^(\$)?[A-Ia-i]?[A-Za-z](\$)?(\d+)\:(\$)?[A-Ia-i]?[A-Za-z](\$)?(\d+)$/",$token))
  566.         {
  567.             return $this->_convertRange2d($token);
  568.         }
  569.         // match ranges like A1..B2
  570.         elseif (preg_match("/^(\$)?[A-Ia-i]?[A-Za-z](\$)?(\d+)\.\.(\$)?[A-Ia-i]?[A-Za-z](\$)?(\d+)$/",$token))
  571.         {
  572.             return $this->_convertRange2d($token);
  573.         }
  574.         // match external ranges like Sheet1!A1 or Sheet1:Sheet2!A1:B2
  575.         elseif (preg_match("/^\w+(\:\w+)?\!([A-Ia-i]?[A-Za-z])?(\d+)\:([A-Ia-i]?[A-Za-z])?(\d+)$/",$token))
  576.         {
  577.             return $this->_convertRange3d($token);
  578.         }
  579.         // match external ranges like 'Sheet1'!A1 or 'Sheet1:Sheet2'!A1:B2
  580.         elseif (preg_match("/^'\w+(\:\w+)?'\!([A-Ia-i]?[A-Za-z])?(\d+)\:([A-Ia-i]?[A-Za-z])?(\d+)$/",$token))
  581.         {
  582.             return $this->_convertRange3d($token);
  583.         }
  584.         elseif (isset($this->ptg[$token])) // operators (including parentheses)
  585.         {
  586.             return pack("C", $this->ptg[$token]);
  587.         }
  588.         // commented so argument number can be processed correctly. See toReversePolish().
  589.         /*elseif (preg_match("/[A-Z0-9\xc0-\xdc\.]+/",$token))
  590.         {
  591.             return($this->_convertFunction($token,$this->_func_args));
  592.         }*/
  593.         // if it's an argument, ignore the token (the argument remains)
  594.         elseif ($token == 'arg')
  595.         {
  596.             return '';
  597.         }
  598.         // TODO: use real error codes
  599.         return $this->raiseError("Unknown token $token");
  600.     }
  601.     
  602.     /**
  603.     * Convert a number token to ptgInt or ptgNum
  604.     *
  605.     * @access private
  606.     * @param mixed $num an integer or double for conversion to its ptg value
  607.     */
  608.     function _convertNumber($num)
  609.     {
  610.         // Integer in the range 0..2**16-1
  611.         if ((preg_match("/^\d+$/",$num)) and ($num <= 65535)) {
  612.             return pack("Cv", $this->ptg['ptgInt'], $num);
  613.         }
  614.         else // A float
  615.         {
  616.             if ($this->_byte_order) { // if it's Big Endian
  617.                 $num = strrev($num);
  618.             }
  619.             return pack("Cd", $this->ptg['ptgNum'], $num);
  620.         }
  621.     }
  622.     
  623.     /**
  624.     * Convert a string token to ptgStr
  625.     *
  626.     * @access private
  627.     * @param string $string A string for conversion to its ptg value. 
  628.     * @return mixed the converted token on success. PEAR_Error if the string
  629.     *               is longer than 255 characters.
  630.     */
  631.     function _convertString($string)
  632.     {
  633.         // chop away beggining and ending quotes
  634.         $string = substr($string, 1, strlen($string) - 2);
  635.         if (strlen($string) > 255) {
  636.             return $this->raiseError("String is too long");
  637.         }
  638.         if ($this->_BIFF_version == 0x0500) {
  639.             return pack("CC", $this->ptg['ptgStr'], strlen($string)).$string;
  640.         }
  641.         elseif ($this->_BIFF_version == 0x0600) {
  642.             $encoding = 0;   // TODO: Unicode support
  643.             return pack("CCC", $this->ptg['ptgStr'], strlen($string), $encoding).$string;
  644.         }
  645.     }
  646.  
  647.     /**
  648.     * Convert a function to a ptgFunc or ptgFuncVarV depending on the number of
  649.     * args that it takes.
  650.     *
  651.     * @access private
  652.     * @param string  $token    The name of the function for convertion to ptg value.
  653.     * @param integer $num_args The number of arguments the function receives.
  654.     * @return string The packed ptg for the function
  655.     */
  656.     function _convertFunction($token, $num_args)
  657.     {
  658.         $args     = $this->_functions[$token][1];
  659.         $volatile = $this->_functions[$token][3];
  660.     
  661.         // Fixed number of args eg. TIME($i,$j,$k).
  662.         if ($args >= 0) {
  663.             return pack("Cv", $this->ptg['ptgFuncV'], $this->_functions[$token][0]);
  664.         }
  665.         // Variable number of args eg. SUM($i,$j,$k, ..).
  666.         if ($args == -1) {
  667.             return pack("CCv", $this->ptg['ptgFuncVarV'], $num_args, $this->_functions[$token][0]);
  668.         }
  669.     }
  670.     
  671.     /**
  672.     * Convert an Excel range such as A1:D4 to a ptgRefV.
  673.     *
  674.     * @access private
  675.     * @param string $range An Excel range in the A1:A2 or A1..A2 format.
  676.     */
  677.     function _convertRange2d($range)
  678.     {
  679.         $class = 2; // as far as I know, this is magick.
  680.     
  681.         // Split the range into 2 cell refs
  682.         if (preg_match("/^([A-Ia-i]?[A-Za-z])(\d+)\:([A-Ia-i]?[A-Za-z])(\d+)$/",$range)) {
  683.             list($cell1, $cell2) = split(':', $range);
  684.         }
  685.         elseif (preg_match("/^([A-Ia-i]?[A-Za-z])(\d+)\.\.([A-Ia-i]?[A-Za-z])(\d+)$/",$range)) {
  686.             list($cell1, $cell2) = split('\.\.', $range);
  687.         
  688.         }
  689.         else {
  690.             // TODO: use real error codes
  691.             return $this->raiseError("Unknown range separator", 0, PEAR_ERROR_DIE);
  692.         }
  693.     
  694.         // Convert the cell references
  695.         $cell_array1 = $this->_cellToPackedRowcol($cell1);
  696.         if (PEAR::isError($cell_array1)) {
  697.             return $cell_array1;
  698.         }
  699.         list($row1, $col1) = $cell_array1;
  700.         $cell_array2 = $this->_cellToPackedRowcol($cell2);
  701.         if (PEAR::isError($cell_array2)) {
  702.             return $cell_array2;
  703.         }
  704.         list($row2, $col2) = $cell_array2;
  705.     
  706.         // The ptg value depends on the class of the ptg.
  707.         if ($class == 0) {
  708.             $ptgArea = pack("C", $this->ptg['ptgArea']);
  709.         }
  710.         elseif ($class == 1) {
  711.             $ptgArea = pack("C", $this->ptg['ptgAreaV']);
  712.         }
  713.         elseif ($class == 2) {
  714.             $ptgArea = pack("C", $this->ptg['ptgAreaA']);
  715.         }
  716.         else {
  717.             // TODO: use real error codes
  718.             return $this->raiseError("Unknown class $class", 0, PEAR_ERROR_DIE);
  719.         }
  720.         return $ptgArea . $row1 . $row2 . $col1. $col2;
  721.     }
  722.  
  723.     /**
  724.     * Convert an Excel 3d range such as "Sheet1!A1:D4" or "Sheet1:Sheet2!A1:D4" to
  725.     * a ptgArea3d.
  726.     *
  727.     * @access private
  728.     * @param string $token An Excel range in the Sheet1!A1:A2 format.
  729.     * @return mixed The packed ptgArea3d token on success, PEAR_Error on failure.
  730.     */
  731.     function _convertRange3d($token)
  732.     {
  733.         $class = 2; // as far as I know, this is magick.
  734.  
  735.         // Split the ref at the ! symbol
  736.         list($ext_ref, $range) = split('!', $token);
  737.  
  738.         // Convert the external reference part (different for BIFF8)
  739.         if ($this->_BIFF_version == 0x0500) {
  740.             $ext_ref = $this->_packExtRef($ext_ref);
  741.             if (PEAR::isError($ext_ref)) {
  742.                 return $ext_ref;
  743.             }
  744.         }
  745.         elseif ($this->_BIFF_version == 0x0600) {
  746.              $ext_ref = $this->_getRefIndex($ext_ref);
  747.              if (PEAR::isError($ext_ref)) {
  748.                  return $ext_ref;
  749.              }
  750.         }
  751.  
  752.         // Split the range into 2 cell refs
  753.         list($cell1, $cell2) = split(':', $range);
  754.  
  755.         // Convert the cell references
  756.         if (preg_match("/^(\$)?[A-Ia-i]?[A-Za-z](\$)?(\d+)$/", $cell1))
  757.         {
  758.             $cell_array1 = $this->_cellToPackedRowcol($cell1);
  759.             if (PEAR::isError($cell_array1)) {
  760.                 return $cell_array1;
  761.             }
  762.             list($row1, $col1) = $cell_array1;
  763.             $cell_array2 = $this->_cellToPackedRowcol($cell2);
  764.             if (PEAR::isError($cell_array2)) {
  765.                 return $cell_array2;
  766.             }
  767.             list($row2, $col2) = $cell_array2;
  768.         }
  769.         else { // It's a rows range (like 26:27)
  770.              $cells_array = $this->_rangeToPackedRange($cell1.':'.$cell2);
  771.              if (PEAR::isError($cells_array)) {
  772.                  return $cells_array;
  773.              }
  774.              list($row1, $col1, $row2, $col2) = $cells_array;
  775.         }
  776.  
  777.         // The ptg value depends on the class of the ptg.
  778.         if ($class == 0) {
  779.             $ptgArea = pack("C", $this->ptg['ptgArea3d']);
  780.         }
  781.         elseif ($class == 1) {
  782.             $ptgArea = pack("C", $this->ptg['ptgArea3dV']);
  783.         }
  784.         elseif ($class == 2) {
  785.             $ptgArea = pack("C", $this->ptg['ptgArea3dA']);
  786.         }
  787.         else {
  788.             return $this->raiseError("Unknown class $class", 0, PEAR_ERROR_DIE);
  789.         }
  790.  
  791.         return $ptgArea . $ext_ref . $row1 . $row2 . $col1. $col2;
  792.     }
  793.  
  794.     /**
  795.     * Convert an Excel reference such as A1, $B2, C$3 or $D$4 to a ptgRefV.
  796.     *
  797.     * @access private
  798.     * @param string $cell An Excel cell reference
  799.     * @return string The cell in packed() format with the corresponding ptg
  800.     */
  801.     function _convertRef2d($cell)
  802.     {
  803.         $class = 2; // as far as I know, this is magick.
  804.     
  805.         // Convert the cell reference
  806.         $cell_array = $this->_cellToPackedRowcol($cell);
  807.         if (PEAR::isError($cell_array)) {
  808.             return $cell_array;
  809.         }
  810.         list($row, $col) = $cell_array;
  811.     
  812.         // The ptg value depends on the class of the ptg.
  813.         if ($class == 0) {
  814.             $ptgRef = pack("C", $this->ptg['ptgRef']);
  815.         }
  816.         elseif ($class == 1) {
  817.             $ptgRef = pack("C", $this->ptg['ptgRefV']);
  818.         }
  819.         elseif ($class == 2) {
  820.             $ptgRef = pack("C", $this->ptg['ptgRefA']);
  821.         }
  822.         else {
  823.             // TODO: use real error codes
  824.             return $this->raiseError("Unknown class $class");
  825.         }
  826.         return $ptgRef.$row.$col;
  827.     }
  828.     
  829.     /**
  830.     * Convert an Excel 3d reference such as "Sheet1!A1" or "Sheet1:Sheet2!A1" to a
  831.     * ptgRef3d.
  832.     *
  833.     * @access private
  834.     * @param string $cell An Excel cell reference
  835.     * @return mixed The packed ptgRef3d token on success, PEAR_Error on failure.
  836.     */
  837.     function _convertRef3d($cell)
  838.     {
  839.         $class = 2; // as far as I know, this is magick.
  840.  
  841.         // Split the ref at the ! symbol
  842.         list($ext_ref, $cell) = split('!', $cell);
  843.  
  844.         // Convert the external reference part (different for BIFF8)
  845.         if ($this->_BIFF_version == 0x0500) {
  846.             $ext_ref = $this->_packExtRef($ext_ref);
  847.             if (PEAR::isError($ext_ref)) {
  848.                 return $ext_ref;
  849.             }
  850.         }
  851.         elseif ($this->_BIFF_version == 0x0600) {
  852.             $ext_ref = $this->_getRefIndex($ext_ref);
  853.             if (PEAR::isError($ext_ref)) {
  854.                 return $ext_ref;
  855.             }
  856.         }
  857.  
  858.         // Convert the cell reference part
  859.         list($row, $col) = $this->_cellToPackedRowcol($cell);
  860.  
  861.         // The ptg value depends on the class of the ptg.
  862.         if ($class == 0) {
  863.             $ptgRef = pack("C", $this->ptg['ptgRef3d']);
  864.         }
  865.         elseif ($class == 1) {
  866.             $ptgRef = pack("C", $this->ptg['ptgRef3dV']);
  867.         }
  868.         elseif ($class == 2) {
  869.             $ptgRef = pack("C", $this->ptg['ptgRef3dA']);
  870.         }
  871.         else {
  872.             return $this->raiseError("Unknown class $class", 0, PEAR_ERROR_DIE);
  873.         }
  874.  
  875.         return $ptgRef . $ext_ref. $row . $col;
  876.     }
  877.  
  878.     /**
  879.     * Convert the sheet name part of an external reference, for example "Sheet1" or
  880.     * "Sheet1:Sheet2", to a packed structure.
  881.     *
  882.     * @access private
  883.     * @param string $ext_ref The name of the external reference
  884.     * @return string The reference index in packed() format
  885.     */
  886.     function _packExtRef($ext_ref)
  887.     {
  888.         $ext_ref = preg_replace("/^'/", '', $ext_ref); // Remove leading  ' if any.
  889.         $ext_ref = preg_replace("/'$/", '', $ext_ref); // Remove trailing ' if any.
  890.  
  891.         // Check if there is a sheet range eg., Sheet1:Sheet2.
  892.         if (preg_match("/:/", $ext_ref))
  893.         {
  894.             list($sheet_name1, $sheet_name2) = split(':', $ext_ref);
  895.  
  896.             $sheet1 = $this->_getSheetIndex($sheet_name1);
  897.             if ($sheet1 == -1) {
  898.                 return $this->raiseError("Unknown sheet name $sheet_name1 in formula");
  899.             }
  900.             $sheet2 = $this->_getSheetIndex($sheet_name2);
  901.             if ($sheet2 == -1) {
  902.                 return $this->raiseError("Unknown sheet name $sheet_name2 in formula");
  903.             }
  904.  
  905.             // Reverse max and min sheet numbers if necessary
  906.             if ($sheet1 > $sheet2) {
  907.                 list($sheet1, $sheet2) = array($sheet2, $sheet1);
  908.             }
  909.         }
  910.         else // Single sheet name only.
  911.         {
  912.             $sheet1 = $this->_getSheetIndex($ext_ref);
  913.             if ($sheet1 == -1) {
  914.                 return $this->raiseError("Unknown sheet name $ext_ref in formula");
  915.             }
  916.             $sheet2 = $sheet1;
  917.         }
  918.  
  919.         // References are stored relative to 0xFFFF.
  920.         $offset = -1 - $sheet1;
  921.  
  922.         return pack('vdvv', $offset, 0x00, $sheet1, $sheet2);
  923.     }
  924.  
  925.     /**
  926.     * Look up the REF index that corresponds to an external sheet name 
  927.     * (or range). If it doesn't exist yet add it to the workbook's references
  928.     * array. It assumes all sheet names given must exist.
  929.     *
  930.     * @access private
  931.     * @param string $ext_ref The name of the external reference
  932.     * @return mixed The reference index in packed() format on success,
  933.     *               PEAR_Error on failure
  934.     */
  935.     function _getRefIndex($ext_ref)
  936.     {
  937.         $ext_ref = preg_replace("/^'/", '', $ext_ref); // Remove leading  ' if any.
  938.         $ext_ref = preg_replace("/'$/", '', $ext_ref); // Remove trailing ' if any.
  939.  
  940.         // Check if there is a sheet range eg., Sheet1:Sheet2.
  941.         if (preg_match("/:/", $ext_ref))
  942.         {
  943.             list($sheet_name1, $sheet_name2) = split(':', $ext_ref);
  944.  
  945.             $sheet1 = $this->_getSheetIndex($sheet_name1);
  946.             if ($sheet1 == -1) {
  947.                 return $this->raiseError("Unknown sheet name $sheet_name1 in formula");
  948.             }
  949.             $sheet2 = $this->_getSheetIndex($sheet_name2);
  950.             if ($sheet2 == -1) {
  951.                 return $this->raiseError("Unknown sheet name $sheet_name2 in formula");
  952.             }
  953.  
  954.             // Reverse max and min sheet numbers if necessary
  955.             if ($sheet1 > $sheet2) {
  956.                 list($sheet1, $sheet2) = array($sheet2, $sheet1);
  957.             }
  958.         }
  959.         else // Single sheet name only.
  960.         {
  961.             $sheet1 = $this->_getSheetIndex($ext_ref);
  962.             if ($sheet1 == -1) {
  963.                 return $this->raiseError("Unknown sheet name $ext_ref in formula");
  964.             }
  965.             $sheet2 = $sheet1;
  966.         }
  967.  
  968.         // assume all references belong to this document
  969.         $supbook_index = 0x00;
  970.         $ref = pack('vvv', $supbook_index, $sheet1, $sheet2);
  971.         $total_references = count($this->_references);
  972.         $index = -1;
  973.         for ($i = 0; $i < $total_references; $i++)
  974.         {
  975.             if ($ref == $this->_references[$i]) {
  976.                 $index = $i;
  977.                 break;
  978.             }
  979.         }
  980.         // if REF was not found add it to references array
  981.         if ($index == -1)
  982.         {
  983.             $this->_references[$total_references] = $ref;
  984.             $index = $total_references;
  985.         }
  986.  
  987.         return pack('v', $index);
  988.     }
  989.  
  990.     /**
  991.     * Look up the index that corresponds to an external sheet name. The hash of
  992.     * sheet names is updated by the addworksheet() method of the 
  993.     * Spreadsheet_Excel_Writer_Workbook class.
  994.     *
  995.     * @access private
  996.     * @return integer The sheet index, -1 if the sheet was not found
  997.     */
  998.     function _getSheetIndex($sheet_name)
  999.     {
  1000.         if (!isset($this->_ext_sheets[$sheet_name])) {
  1001.             return -1;
  1002.         }
  1003.         else {
  1004.             return $this->_ext_sheets[$sheet_name];
  1005.         }
  1006.     }
  1007.  
  1008.     /**
  1009.     * This method is used to update the array of sheet names. It is
  1010.     * called by the addWorksheet() method of the
  1011.     * Spreadsheet_Excel_Writer_Workbook class.
  1012.     *
  1013.     * @access public
  1014.     * @see Spreadsheet_Excel_Writer_Workbook::addWorksheet()
  1015.     * @param string  $name  The name of the worksheet being added
  1016.     * @param integer $index The index of the worksheet being added
  1017.     */
  1018.     function setExtSheet($name, $index)
  1019.     {
  1020.         $this->_ext_sheets[$name] = $index;
  1021.     }
  1022.  
  1023.     /**
  1024.     * pack() row and column into the required 3 or 4 byte format.
  1025.     *
  1026.     * @access private
  1027.     * @param string $cell The Excel cell reference to be packed
  1028.     * @return array Array containing the row and column in packed() format
  1029.     */
  1030.     function _cellToPackedRowcol($cell)
  1031.     {
  1032.         $cell = strtoupper($cell);
  1033.         list($row, $col, $row_rel, $col_rel) = $this->_cellToRowcol($cell);
  1034.         if ($col >= 256) {
  1035.             return $this->raiseError("Column in: $cell greater than 255");
  1036.         }
  1037.         // FIXME: change for BIFF8
  1038.         if ($row >= 16384) {
  1039.             return $this->raiseError("Row in: $cell greater than 16384 ");
  1040.         }
  1041.     
  1042.         // Set the high bits to indicate if row or col are relative.
  1043.         if ($this->_BIFF_version == 0x0500) {
  1044.             $row    |= $col_rel << 14;
  1045.             $row    |= $row_rel << 15;
  1046.             $col     = pack('C', $col);
  1047.         }
  1048.         elseif ($this->_BIFF_version == 0x0600) {
  1049.             $col    |= $col_rel << 14;
  1050.             $col    |= $row_rel << 15;
  1051.             $col     = pack('v', $col);
  1052.         }
  1053.         $row     = pack('v', $row);
  1054.     
  1055.         return array($row, $col);
  1056.     }
  1057.     
  1058.     /**
  1059.     * pack() row range into the required 3 or 4 byte format.
  1060.     * Just using maximum col/rows, which is probably not the correct solution
  1061.     *
  1062.     * @access private
  1063.     * @param string $range The Excel range to be packed
  1064.     * @return array Array containing (row1,col1,row2,col2) in packed() format
  1065.     */
  1066.     function _rangeToPackedRange($range)
  1067.     {
  1068.         preg_match('/(\$)?(\d+)\:(\$)?(\d+)/', $range, $match);
  1069.         // return absolute rows if there is a $ in the ref
  1070.         $row1_rel = empty($match[1]) ? 1 : 0;
  1071.         $row1     = $match[2];
  1072.         $row2_rel = empty($match[3]) ? 1 : 0;
  1073.         $row2     = $match[4];
  1074.         // Convert 1-index to zero-index
  1075.         $row1--;
  1076.         $row2--;
  1077.         // Trick poor inocent Excel
  1078.         $col1 = 0;
  1079.         $col2 = 16383; // FIXME: maximum possible value for Excel 5 (change this!!!)
  1080.  
  1081.         // FIXME: this changes for BIFF8
  1082.         if (($row1 >= 16384) or ($row2 >= 16384)) {
  1083.             return $this->raiseError("Row in: $range greater than 16384 ");
  1084.         }
  1085.     
  1086.         // Set the high bits to indicate if rows are relative.
  1087.         if ($this->_BIFF_version == 0x0500) {
  1088.             $row1    |= $row1_rel << 14; // FIXME: probably a bug
  1089.             $row2    |= $row2_rel << 15;
  1090.             $col1     = pack('C', $col1);
  1091.             $col2     = pack('C', $col2);
  1092.         }
  1093.         elseif ($this->_BIFF_version == 0x0600) {
  1094.             $col1    |= $row1_rel << 15;
  1095.             $col2    |= $row2_rel << 15;
  1096.             $col1     = pack('v', $col1);
  1097.             $col2     = pack('v', $col2);
  1098.         }
  1099.         $row1     = pack('v', $row1);
  1100.         $row2     = pack('v', $row2);
  1101.     
  1102.         return array($row1, $col1, $row2, $col2);
  1103.     }
  1104.  
  1105.     /**
  1106.     * Convert an Excel cell reference such as A1 or $B2 or C$3 or $D$4 to a zero
  1107.     * indexed row and column number. Also returns two (0,1) values to indicate
  1108.     * whether the row or column are relative references.
  1109.     *
  1110.     * @access private
  1111.     * @param string $cell The Excel cell reference in A1 format.
  1112.     * @return array
  1113.     */
  1114.     function _cellToRowcol($cell)
  1115.     {
  1116.         preg_match('/(\$)?([A-I]?[A-Z])(\$)?(\d+)/',$cell,$match);
  1117.         // return absolute column if there is a $ in the ref
  1118.         $col_rel = empty($match[1]) ? 1 : 0;
  1119.         $col_ref = $match[2];
  1120.         $row_rel = empty($match[3]) ? 1 : 0;
  1121.         $row     = $match[4];
  1122.         
  1123.         // Convert base26 column string to a number.
  1124.         $expn   = strlen($col_ref) - 1;
  1125.         $col    = 0;
  1126.         for ($i=0; $i < strlen($col_ref); $i++)
  1127.         {
  1128.             $col += (ord($col_ref{$i}) - ord('A') + 1) * pow(26, $expn);
  1129.             $expn--;
  1130.         }
  1131.     
  1132.         // Convert 1-index to zero-index
  1133.         $row--;
  1134.         $col--;
  1135.     
  1136.         return array($row, $col, $row_rel, $col_rel);
  1137.     }
  1138.     
  1139.     /**
  1140.     * Advance to the next valid token.
  1141.     *
  1142.     * @access private
  1143.     */
  1144.     function _advance()
  1145.     {
  1146.         $i = $this->_current_char;
  1147.         // eat up white spaces
  1148.         if ($i < strlen($this->_formula))
  1149.         {
  1150.             while ($this->_formula{$i} == " ") {
  1151.                 $i++;
  1152.             }
  1153.             if ($i < strlen($this->_formula) - 1) {
  1154.                 $this->_lookahead = $this->_formula{$i+1};
  1155.             }
  1156.             $token = "";
  1157.         }
  1158.         while ($i < strlen($this->_formula))
  1159.         {
  1160.             $token .= $this->_formula{$i};
  1161.             if ($i < strlen($this->_formula) - 1) {
  1162.                 $this->_lookahead = $this->_formula{$i+1};
  1163.             }
  1164.             else {
  1165.                 $this->_lookahead = '';
  1166.             }
  1167.             if ($this->_match($token) != '')
  1168.             {
  1169.                 //if ($i < strlen($this->_formula) - 1) {
  1170.                 //    $this->_lookahead = $this->_formula{$i+1};
  1171.                 //}
  1172.                 $this->_current_char = $i + 1;
  1173.                 $this->_current_token = $token;
  1174.                 return 1;
  1175.             }
  1176.             if ($i < strlen($this->_formula) - 2) {
  1177.                 $this->_lookahead = $this->_formula{$i+2};
  1178.             }
  1179.             // if we run out of characters _lookahead becomes empty
  1180.             else {
  1181.                 $this->_lookahead = '';
  1182.             }
  1183.             $i++;
  1184.         }
  1185.         //die("Lexical error ".$this->_current_char);
  1186.     }
  1187.     
  1188.     /**
  1189.     * Checks if it's a valid token.
  1190.     *
  1191.     * @access private
  1192.     * @param mixed $token The token to check.
  1193.     * @return mixed       The checked token or false on failure
  1194.     */
  1195.     function _match($token)
  1196.     {
  1197.         switch($token)
  1198.         {
  1199.             case SPREADSHEET_EXCEL_WRITER_ADD:
  1200.                 return $token;
  1201.                 break;
  1202.             case SPREADSHEET_EXCEL_WRITER_SUB:
  1203.                 return $token;
  1204.                 break;
  1205.             case SPREADSHEET_EXCEL_WRITER_MUL:
  1206.                 return $token;
  1207.                 break;
  1208.             case SPREADSHEET_EXCEL_WRITER_DIV:
  1209.                 return $token;
  1210.                 break;
  1211.             case SPREADSHEET_EXCEL_WRITER_OPEN:
  1212.                 return $token;
  1213.                 break;
  1214.             case SPREADSHEET_EXCEL_WRITER_CLOSE:
  1215.                 return $token;
  1216.                 break;
  1217.             case SPREADSHEET_EXCEL_WRITER_COMA:
  1218.                 return $token;
  1219.                 break;
  1220.             case SPREADSHEET_EXCEL_WRITER_SEMICOLON:
  1221.                 return $token;
  1222.                 break;
  1223.             case SPREADSHEET_EXCEL_WRITER_GT:
  1224.                 if ($this->_lookahead == '=') { // it's a GE token
  1225.                     break;
  1226.                 }
  1227.                 return $token;
  1228.                 break;
  1229.             case SPREADSHEET_EXCEL_WRITER_LT:
  1230.                 // it's a LE or a NE token
  1231.                 if (($this->_lookahead == '=') or ($this->_lookahead == '>')) {
  1232.                     break;
  1233.                 }
  1234.                 return $token;
  1235.                 break;
  1236.             case SPREADSHEET_EXCEL_WRITER_GE:
  1237.                 return $token;
  1238.                 break;
  1239.             case SPREADSHEET_EXCEL_WRITER_LE:
  1240.                 return $token;
  1241.                 break;
  1242.             case SPREADSHEET_EXCEL_WRITER_EQ:
  1243.                 return $token;
  1244.                 break;
  1245.             case SPREADSHEET_EXCEL_WRITER_NE:
  1246.                 return $token;
  1247.                 break;
  1248.             default:
  1249.                 // if it's a reference
  1250.                 if (preg_match('/^\$?[A-Ia-i]?[A-Za-z]\$?[0-9]+$/',$token) and
  1251.                    !ereg("[0-9]",$this->_lookahead) and 
  1252.                    ($this->_lookahead != ':') and ($this->_lookahead != '.') and
  1253.                    ($this->_lookahead != '!'))
  1254.                 {
  1255.                     return $token;
  1256.                 }
  1257.                 // If it's an external reference (Sheet1!A1 or Sheet1:Sheet2!A1)
  1258.                 elseif (preg_match("/^\w+(\:\w+)?\![A-Ia-i]?[A-Za-z][0-9]+$/",$token) and
  1259.                        !ereg("[0-9]",$this->_lookahead) and
  1260.                        ($this->_lookahead != ':') and ($this->_lookahead != '.'))
  1261.                 {
  1262.                     return $token;
  1263.                 }
  1264.                 // If it's an external reference (Sheet1!A1 or Sheet1:Sheet2!A1)
  1265.                 elseif (preg_match("/^'\w+(\:\w+)?'\![A-Ia-i]?[A-Za-z][0-9]+$/",$token) and
  1266.                        !ereg("[0-9]",$this->_lookahead) and
  1267.                        ($this->_lookahead != ':') and ($this->_lookahead != '.'))
  1268.                 {
  1269.                     return $token;
  1270.                 }
  1271.                 // if it's a range (A1:A2)
  1272.                 elseif (preg_match("/^(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+:(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+$/",$token) and 
  1273.                        !ereg("[0-9]",$this->_lookahead))
  1274.                 {
  1275.                     return $token;
  1276.                 }
  1277.                 // if it's a range (A1..A2)
  1278.                 elseif (preg_match("/^(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+\.\.(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+$/",$token) and 
  1279.                        !ereg("[0-9]",$this->_lookahead))
  1280.                 {
  1281.                     return $token;
  1282.                 }
  1283.                 // If it's an external range like Sheet1!A1 or Sheet1:Sheet2!A1:B2
  1284.                 elseif (preg_match("/^\w+(\:\w+)?\!([A-Ia-i]?[A-Za-z])?[0-9]+:([A-Ia-i]?[A-Za-z])?[0-9]+$/",$token) and
  1285.                        !ereg("[0-9]",$this->_lookahead))
  1286.                 {
  1287.                     return $token;
  1288.                 }
  1289.                 // If it's an external range like 'Sheet1'!A1 or 'Sheet1:Sheet2'!A1:B2
  1290.                 elseif (preg_match("/^'\w+(\:\w+)?'\!([A-Ia-i]?[A-Za-z])?[0-9]+:([A-Ia-i]?[A-Za-z])?[0-9]+$/",$token) and
  1291.                        !ereg("[0-9]",$this->_lookahead))
  1292.                 {
  1293.                     return $token;
  1294.                 }
  1295.                 // If it's a number (check that it's not a sheet name or range)
  1296.                 elseif (is_numeric($token) and 
  1297.                         (!is_numeric($token.$this->_lookahead) or ($this->_lookahead == '')) and
  1298.                         ($this->_lookahead != '!') and ($this->_lookahead != ':'))
  1299.                 {
  1300.                     return $token;
  1301.                 }
  1302.                 // If it's a string (of maximum 255 characters)
  1303.                 elseif (ereg("^\"[^\"]{0,255}\"$",$token))
  1304.                 {
  1305.                     return $token;
  1306.                 }
  1307.                 // if it's a function call
  1308.                 elseif (eregi("^[A-Z0-9\xc0-\xdc\.]+$",$token) and ($this->_lookahead == "("))
  1309.                 {
  1310.                     return $token;
  1311.                 }
  1312.                 return '';
  1313.         }
  1314.     }
  1315.     
  1316.     /**
  1317.     * The parsing method. It parses a formula.
  1318.     *
  1319.     * @access public
  1320.     * @param string $formula The formula to parse, without the initial equal
  1321.     *                        sign (=).
  1322.     * @return mixed true on success, PEAR_Error on failure
  1323.     */
  1324.     function parse($formula)
  1325.     {
  1326.         $this->_current_char = 0;
  1327.         $this->_formula      = $formula;
  1328.         $this->_lookahead    = $formula{1};
  1329.         $this->_advance();
  1330.         $this->_parse_tree   = $this->_condition();
  1331.         if (PEAR::isError($this->_parse_tree)) {
  1332.             return $this->_parse_tree;
  1333.         }
  1334.         return true;
  1335.     }
  1336.     
  1337.     /**
  1338.     * It parses a condition. It assumes the following rule:
  1339.     * Cond -> Expr [(">" | "<") Expr]
  1340.     *
  1341.     * @access private
  1342.     * @return mixed The parsed ptg'd tree on success, PEAR_Error on failure
  1343.     */
  1344.     function _condition()
  1345.     {
  1346.         $result = $this->_expression();
  1347.         if (PEAR::isError($result)) {
  1348.             return $result;
  1349.         }
  1350.         if ($this->_current_token == SPREADSHEET_EXCEL_WRITER_LT)
  1351.         {
  1352.             $this->_advance();
  1353.             $result2 = $this->_expression();
  1354.             if (PEAR::isError($result2)) {
  1355.                 return $result2;
  1356.             }
  1357.             $result = $this->_createTree('ptgLT', $result, $result2);
  1358.         }
  1359.         elseif ($this->_current_token == SPREADSHEET_EXCEL_WRITER_GT) 
  1360.         {
  1361.             $this->_advance();
  1362.             $result2 = $this->_expression();
  1363.             if (PEAR::isError($result2)) {
  1364.                 return $result2;
  1365.             }
  1366.             $result = $this->_createTree('ptgGT', $result, $result2);
  1367.         }
  1368.         elseif ($this->_current_token == SPREADSHEET_EXCEL_WRITER_LE) 
  1369.         {
  1370.             $this->_advance();
  1371.             $result2 = $this->_expression();
  1372.             if (PEAR::isError($result2)) {
  1373.                 return $result2;
  1374.             }
  1375.             $result = $this->_createTree('ptgLE', $result, $result2);
  1376.         }
  1377.         elseif ($this->_current_token == SPREADSHEET_EXCEL_WRITER_GE) 
  1378.         {
  1379.             $this->_advance();
  1380.             $result2 = $this->_expression();
  1381.             if (PEAR::isError($result2)) {
  1382.                 return $result2;
  1383.             }
  1384.             $result = $this->_createTree('ptgGE', $result, $result2);
  1385.         }
  1386.         elseif ($this->_current_token == SPREADSHEET_EXCEL_WRITER_EQ) 
  1387.         {
  1388.             $this->_advance();
  1389.             $result2 = $this->_expression();
  1390.             if (PEAR::isError($result2)) {
  1391.                 return $result2;
  1392.             }
  1393.             $result = $this->_createTree('ptgEQ', $result, $result2);
  1394.         }
  1395.         elseif ($this->_current_token == SPREADSHEET_EXCEL_WRITER_NE) 
  1396.         {
  1397.             $this->_advance();
  1398.             $result2 = $this->_expression();
  1399.             if (PEAR::isError($result2)) {
  1400.                 return $result2;
  1401.             }
  1402.             $result = $this->_createTree('ptgNE', $result, $result2);
  1403.         }
  1404.         return $result;
  1405.     }
  1406.  
  1407.     /**
  1408.     * It parses a expression. It assumes the following rule:
  1409.     * Expr -> Term [("+" | "-") Term]
  1410.     *      -> "string"
  1411.     *      -> "-" Term
  1412.     *
  1413.     * @access private
  1414.     * @return mixed The parsed ptg'd tree on success, PEAR_Error on failure
  1415.     */
  1416.     function _expression()
  1417.     {
  1418.         // If it's a string return a string node
  1419.         if (ereg("^\"[^\"]{0,255}\"$", $this->_current_token)) {
  1420.             $result = $this->_createTree($this->_current_token, '', '');
  1421.             $this->_advance();
  1422.             return $result;
  1423.         }
  1424.         // catch "-" Term
  1425.         elseif ($this->_current_token == SPREADSHEET_EXCEL_WRITER_SUB) {
  1426.             $this->_advance();
  1427.             $result2 = $this->_expression();
  1428.             $result = $this->_createTree('ptgUminus', $result2, '');
  1429.             return $result;
  1430.         }
  1431.         $result = $this->_term();
  1432.         if (PEAR::isError($result)) {
  1433.             return $result;
  1434.         }
  1435.         while (($this->_current_token == SPREADSHEET_EXCEL_WRITER_ADD) or 
  1436.                ($this->_current_token == SPREADSHEET_EXCEL_WRITER_SUB))
  1437.         {
  1438.             if ($this->_current_token == SPREADSHEET_EXCEL_WRITER_ADD)
  1439.             {
  1440.                 $this->_advance();
  1441.                 $result2 = $this->_term();
  1442.                 if (PEAR::isError($result2)) {
  1443.                     return $result2;
  1444.                 }
  1445.                 $result = $this->_createTree('ptgAdd', $result, $result2);
  1446.             }
  1447.             else 
  1448.             {
  1449.                 $this->_advance();
  1450.                 $result2 = $this->_term();
  1451.                 if (PEAR::isError($result2)) {
  1452.                     return $result2;
  1453.                 }
  1454.                 $result = $this->_createTree('ptgSub', $result, $result2);
  1455.             }
  1456.         }
  1457.         return $result;
  1458.     }
  1459.     
  1460.     /**
  1461.     * This function just introduces a ptgParen element in the tree, so that Excel
  1462.     * doesn't get confused when working with a parenthesized formula afterwards.
  1463.     *
  1464.     * @access private
  1465.     * @see _fact()
  1466.     * @return array The parsed ptg'd tree
  1467.     */
  1468.     function _parenthesizedExpression()
  1469.     {
  1470.         $result = $this->_createTree('ptgParen', $this->_expression(), '');
  1471.         return $result;
  1472.     }
  1473.     
  1474.     /**
  1475.     * It parses a term. It assumes the following rule:
  1476.     * Term -> Fact [("*" | "/") Fact]
  1477.     *
  1478.     * @access private
  1479.     * @return mixed The parsed ptg'd tree on success, PEAR_Error on failure
  1480.     */
  1481.     function _term()
  1482.     {
  1483.         $result = $this->_fact();
  1484.         if (PEAR::isError($result)) {
  1485.             return $result;
  1486.         }
  1487.         while (($this->_current_token == SPREADSHEET_EXCEL_WRITER_MUL) or 
  1488.                ($this->_current_token == SPREADSHEET_EXCEL_WRITER_DIV))
  1489.         {
  1490.             if ($this->_current_token == SPREADSHEET_EXCEL_WRITER_MUL)
  1491.             {
  1492.                 $this->_advance();
  1493.                 $result2 = $this->_fact();
  1494.                 if (PEAR::isError($result2)) {
  1495.                     return $result2;
  1496.                 }
  1497.                 $result = $this->_createTree('ptgMul', $result, $result2);
  1498.             }
  1499.             else 
  1500.             {
  1501.                 $this->_advance();
  1502.                 $result2 = $this->_fact();
  1503.                 if (PEAR::isError($result2)) {
  1504.                     return $result2;
  1505.                 }
  1506.                 $result = $this->_createTree('ptgDiv', $result, $result2);
  1507.             }
  1508.         }
  1509.         return $result;
  1510.     }
  1511.     
  1512.     /**
  1513.     * It parses a factor. It assumes the following rule:
  1514.     * Fact -> ( Expr )
  1515.     *       | CellRef
  1516.     *       | CellRange
  1517.     *       | Number
  1518.     *       | Function
  1519.     *
  1520.     * @access private
  1521.     * @return mixed The parsed ptg'd tree on success, PEAR_Error on failure
  1522.     */
  1523.     function _fact()
  1524.     {
  1525.         if ($this->_current_token == SPREADSHEET_EXCEL_WRITER_OPEN)
  1526.         {
  1527.             $this->_advance();         // eat the "("
  1528.             $result = $this->_parenthesizedExpression();
  1529.             if ($this->_current_token != SPREADSHEET_EXCEL_WRITER_CLOSE) {
  1530.                 return $this->raiseError("')' token expected.");
  1531.             }
  1532.             $this->_advance();         // eat the ")"
  1533.             return $result;
  1534.         }
  1535.         // if it's a reference
  1536.         if (preg_match('/^\$?[A-Ia-i]?[A-Za-z]\$?[0-9]+$/',$this->_current_token))
  1537.         {
  1538.             $result = $this->_createTree($this->_current_token, '', '');
  1539.             $this->_advance();
  1540.             return $result;
  1541.         }
  1542.         // If it's an external reference (Sheet1!A1 or Sheet1:Sheet2!A1)
  1543.         elseif (preg_match("/^\w+(\:\w+)?\![A-Ia-i]?[A-Za-z][0-9]+$/",$this->_current_token))
  1544.         {
  1545.             $result = $this->_createTree($this->_current_token, '', '');
  1546.             $this->_advance();
  1547.             return $result;
  1548.         }
  1549.         // If it's an external reference (Sheet1!A1 or Sheet1:Sheet2!A1)
  1550.         elseif (preg_match("/^'\w+(\:\w+)?'\![A-Ia-i]?[A-Za-z][0-9]+$/",$this->_current_token))
  1551.         {
  1552.             $result = $this->_createTree($this->_current_token, '', '');
  1553.             $this->_advance();
  1554.             return $result;
  1555.         }
  1556.         // if it's a range
  1557.         elseif (preg_match("/^(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+:(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+$/",$this->_current_token) or 
  1558.                 preg_match("/^(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+\.\.(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+$/",$this->_current_token))
  1559.         {
  1560.             $result = $this->_current_token;
  1561.             $this->_advance();
  1562.             return $result;
  1563.         }
  1564.         // If it's an external range (Sheet1!A1 or Sheet1!A1:B2)
  1565.         elseif (preg_match("/^\w+(\:\w+)?\!([A-Ia-i]?[A-Za-z])?[0-9]+:([A-Ia-i]?[A-Za-z])?[0-9]+$/",$this->_current_token))
  1566.         {
  1567.             $result = $this->_current_token;
  1568.             $this->_advance();
  1569.             return $result;
  1570.         }
  1571.         // If it's an external range ('Sheet1'!A1 or 'Sheet1'!A1:B2)
  1572.         elseif (preg_match("/^'\w+(\:\w+)?'\!([A-Ia-i]?[A-Za-z])?[0-9]+:([A-Ia-i]?[A-Za-z])?[0-9]+$/",$this->_current_token))
  1573.         {
  1574.             $result = $this->_current_token;
  1575.             $this->_advance();
  1576.             return $result;
  1577.         }
  1578.         elseif (is_numeric($this->_current_token))
  1579.         {
  1580.             $result = $this->_createTree($this->_current_token, '', '');
  1581.             $this->_advance();
  1582.             return $result;
  1583.         }
  1584.         // if it's a function call
  1585.         elseif (eregi("^[A-Z0-9\xc0-\xdc\.]+$",$this->_current_token))
  1586.         {
  1587.             $result = $this->_func();
  1588.             return $result;
  1589.         }
  1590.         return $this->raiseError("Syntax error: ".$this->_current_token.
  1591.                                  ", lookahead: ".$this->_lookahead.
  1592.                                  ", current char: ".$this->_current_char);
  1593.     }
  1594.     
  1595.     /**
  1596.     * It parses a function call. It assumes the following rule:
  1597.     * Func -> ( Expr [,Expr]* )
  1598.     *
  1599.     * @access private
  1600.     * @return mixed The parsed ptg'd tree on success, PEAR_Error on failure
  1601.     */
  1602.     function _func()
  1603.     {
  1604.         $num_args = 0; // number of arguments received
  1605.         $function = $this->_current_token;
  1606.         $this->_advance();
  1607.         $this->_advance();         // eat the "("
  1608.         while ($this->_current_token != ')')
  1609.         {
  1610.             if ($num_args > 0)
  1611.             {
  1612.                 if ($this->_current_token == SPREADSHEET_EXCEL_WRITER_COMA ||
  1613.                     $this->_current_token == SPREADSHEET_EXCEL_WRITER_SEMICOLON)
  1614.                 {
  1615.                     $this->_advance();  // eat the "," or ";"
  1616.                 }
  1617.                 else {
  1618.                     return $this->raiseError("Syntax error: comma expected in ".
  1619.                                       "function $function, arg #{$num_args}");
  1620.                 }
  1621.                 $result2 = $this->_condition();
  1622.                 if (PEAR::isError($result2)) {
  1623.                     return $result2;
  1624.                 }
  1625.                 $result = $this->_createTree('arg', $result, $result2);
  1626.             }
  1627.             else // first argument
  1628.             {
  1629.                 $result2 = $this->_condition();
  1630.                 if (PEAR::isError($result2)) {
  1631.                     return $result2;
  1632.                 }
  1633.                 $result = $this->_createTree('arg', '', $result2);
  1634.             }
  1635.             $num_args++;
  1636.         }
  1637.         $args = $this->_functions[$function][1];
  1638.         // If fixed number of args eg. TIME($i,$j,$k). Check that the number of args is valid.
  1639.         if (($args >= 0) and ($args != $num_args)) {
  1640.             return $this->raiseError("Incorrect number of arguments in function $function() ");
  1641.         }
  1642.     
  1643.         $result = $this->_createTree($function, $result, $num_args);
  1644.         $this->_advance();         // eat the ")"
  1645.         return $result;
  1646.     }
  1647.     
  1648.     /**
  1649.     * Creates a tree. In fact an array which may have one or two arrays (sub-trees)
  1650.     * as elements.
  1651.     *
  1652.     * @access private
  1653.     * @param mixed $value The value of this node.
  1654.     * @param mixed $left  The left array (sub-tree) or a final node.
  1655.     * @param mixed $right The right array (sub-tree) or a final node.
  1656.     * @return array A tree
  1657.     */
  1658.     function _createTree($value, $left, $right)
  1659.     {
  1660.         return array('value' => $value, 'left' => $left, 'right' => $right);
  1661.     }
  1662.     
  1663.     /**
  1664.     * Builds a string containing the tree in reverse polish notation (What you 
  1665.     * would use in a HP calculator stack).
  1666.     * The following tree:
  1667.     * 
  1668.     *    +
  1669.     *   / \
  1670.     *  2   3
  1671.     *
  1672.     * produces: "23+"
  1673.     *
  1674.     * The following tree:
  1675.     *
  1676.     *    +
  1677.     *   / \
  1678.     *  3   *
  1679.     *     / \
  1680.     *    6   A1
  1681.     *
  1682.     * produces: "36A1*+"
  1683.     *
  1684.     * In fact all operands, functions, references, etc... are written as ptg's
  1685.     *
  1686.     * @access public
  1687.     * @param array $tree The optional tree to convert.
  1688.     * @return string The tree in reverse polish notation
  1689.     */
  1690.     function toReversePolish($tree = array())
  1691.     {
  1692.         $polish = ""; // the string we are going to return
  1693.         if (empty($tree)) // If it's the first call use _parse_tree
  1694.         {
  1695.             $tree = $this->_parse_tree;
  1696.         }
  1697.         if (is_array($tree['left']))
  1698.         {
  1699.             $converted_tree = $this->toReversePolish($tree['left']);
  1700.             if (PEAR::isError($converted_tree)) {
  1701.                 return $converted_tree;
  1702.             }
  1703.             $polish .= $converted_tree;
  1704.         }
  1705.         elseif ($tree['left'] != '') // It's a final node
  1706.         {
  1707.             $converted_tree = $this->_convert($tree['left']);
  1708.             if (PEAR::isError($converted_tree)) {
  1709.                 return $converted_tree;
  1710.             }
  1711.             $polish .= $converted_tree;
  1712.         }
  1713.         if (is_array($tree['right']))
  1714.         {
  1715.             $converted_tree = $this->toReversePolish($tree['right']);
  1716.             if (PEAR::isError($converted_tree)) {
  1717.                 return $converted_tree;
  1718.             }
  1719.             $polish .= $converted_tree;
  1720.         }
  1721.         elseif ($tree['right'] != '') // It's a final node
  1722.         {
  1723.             $converted_tree = $this->_convert($tree['right']);
  1724.             if (PEAR::isError($converted_tree)) {
  1725.                 return $converted_tree;
  1726.             }
  1727.             $polish .= $converted_tree;
  1728.         }
  1729.         // if it's a function convert it here (so we can set it's arguments)
  1730.         if (preg_match("/^[A-Z0-9\xc0-\xdc\.]+$/",$tree['value']) and
  1731.             !preg_match('/^([A-Ia-i]?[A-Za-z])(\d+)$/',$tree['value']) and
  1732.             !preg_match("/^[A-Ia-i]?[A-Za-z](\d+)\.\.[A-Ia-i]?[A-Za-z](\d+)$/",$tree['value']) and
  1733.             !is_numeric($tree['value']) and
  1734.             !isset($this->ptg[$tree['value']]))
  1735.         {
  1736.             // left subtree for a function is always an array.
  1737.             if ($tree['left'] != '') {
  1738.                 $left_tree = $this->toReversePolish($tree['left']);
  1739.             }
  1740.             else {
  1741.                 $left_tree = '';
  1742.             }
  1743.             if (PEAR::isError($left_tree)) {
  1744.                 return $left_tree;
  1745.             }
  1746.             // add it's left subtree and return.
  1747.             return $left_tree.$this->_convertFunction($tree['value'], $tree['right']);
  1748.         }
  1749.         else
  1750.         {
  1751.             $converted_tree = $this->_convert($tree['value']);
  1752.             if (PEAR::isError($converted_tree)) {
  1753.                 return $converted_tree;
  1754.             }
  1755.         }
  1756.         $polish .= $converted_tree;
  1757.         return $polish;
  1758.     }
  1759. }
  1760. ?>
  1761.